﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using SchemaMonitor.json;

namespace SchemaMonitor
{
    [DataContract]
    public class CDiffList : List<CDiff>
    {
        public CDiffList() : base() { }
        public CDiffList(int count) : base(count) { }
        public CDiffList(IEnumerable<CDiff> list) : base(list) { }

        private List<int> _parentTagIds;

        //public CDiffList(CFieldComposite b, CFieldComposite a, List<int> parentTagIds) : this(b.AsCompositeList, a.AsCompositeList, parentTagIds) { }
        public CDiffList(CFieldComposite b,  CFieldComposite a, List<int> parentTagIds) : this(b.AsComposite.Fields, a.AsComposite.Fields, parentTagIds) { }
        public CDiffList(CNodeRoot       b,  CNodeRoot       a) : this(a.Fields, b.Fields) { }
        public CDiffList(CFieldList  before, CFieldList  after) : this(before, after, new List<int>(0) { }) { }
        private CDiffList(CFieldList before, CFieldList  after, List<int> parentTagIds)
        {
            _parentTagIds = parentTagIds;

            var b = before.ToDict();
            var a = after.ToDict();
            foreach (var i in a)
            {
                var tagId = i.Key;
                var prev = i.Value;

                CField post = null;
                if (!b.TryGetValue(tagId, out post))
                {
                    Delete(prev);
                    continue;
                }

                Compare(prev, post);
            }
            foreach (var i in b)
            {
                var tagId = i.Key;
                var post = i.Value;
                if (!a.ContainsKey(tagId))
                    Insert(post);
            }
        }

        private List<int> Clone(int tagId)
        {
            var p = new List<int> (_parentTagIds.Count + 1);
            p.AddRange(_parentTagIds);
            p.Add(tagId);
            return p;
        }
        private void Compare(CField before, CField after)
        {
            var t = before.Tag;
            if (t.FieldType.IsSystem || t.FieldType.IsReference)
            {
                if (before.AsBase64() == after.AsBase64())
                    return;
                Update(before, after);
				return;
            }

            //Composite case
            var b = (CFieldComposite)before;
            var a = (CFieldComposite)after;
            try
            {
				if (a.Tag.TagIsList)
				{
					var aa = a.AsCompositeList;
					var bb = b.AsCompositeList;

					var shortcut = true;
					if (aa.Count == bb.Count)
						for (var i = 0; i < aa.Count; i++)
							if (shortcut && aa[i].Hash() != bb[i].Hash())
								shortcut = false;
					if (shortcut)
						return;

					//Full Diff (index, get the guids)
                    var da = new Dictionary<Guid, CNodeBasic>();
                    var db = new Dictionary<Guid, CNodeBasic>();
                    foreach (var i in aa)
                        da.Add(i.Hash(), i);
                    foreach (var i in bb)
                        db.Add(i.Hash(), i);
                    var xa = new List<Guid>(da.Keys);
                    var xb = new List<Guid>(db.Keys);

					//Full Diff (resolve, as sets)
					foreach (var i in xa)
						if (!xb.Contains(i))
							foreach (var f in da[i].Fields)
								Delete(f);
					foreach (var i in xb)
						if (!xa.Contains(i))
							foreach (var f in db[i].Fields)
								Insert(f);
                }
                else
                    this.AddRange(new CDiffList(b, a, Clone(t.TagId)));
            }
            catch
            { }
        }
        private void Insert(CField after) { Add(after.TagId, null, after); }
        private void Delete(CField before) { Add(before.TagId, before, null); }
        private void Update(CField before, CField after) { Add(before.TagId, before, after); }
        private void Add(int tagId, CField before, CField after)
        {
            CTag t = CTag.Cache.GetById(tagId);
            switch (t.TagName)
            {
                case CNodeRoot.PHOTO_IDS:
                case CNodeRoot.CT:
                case "updated_time":
                case "attachments":
                case "subattachments":
                case "type":
                    return;
            }

            var d = new CDiff()
            {
                ParentTagIds = Clone(tagId),
                After = after,
                Before = before
            };
            Add(d);
        }
    }
}
